home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 7: Sunsite
/
Linux Cubed Series 7 - Sunsite Vol 1.iso
/
system
/
news
/
inn1.000
/
inn1.4sec-linux-src.tar
/
inn
/
innd
/
innd.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-03-18
|
20KB
|
893 lines
/* $Revision: 1.39 $
**
** Variable definitions, miscellany, and main().
*/
#define DEFINE_DATA
#include "innd.h"
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#if NOFILE_LIMIT > 0
#include <sys/resource.h>
#endif /* NOFILE_LIMIT > 0 */
#if defined(DO_FAST_RESOLV)
#include <arpa/nameser.h>
#include <resolv.h>
#endif /* defined(DO_FAST_RESOLV) */
#if defined(HAVE_SETBUFFER)
#define SETBUFFER(F, buff, size) setbuffer((F), (buff), (size))
STATIC int LogBufferSize = 4096;
#else
#define SETBUFFER(F, buff, size) setbuf((F), (buff))
STATIC int LogBufferSize = BUFSIZ;
#endif /* defined(HAVE_SETBUFFER) */
BOOL AmRoot = TRUE;
BOOL BufferedLogs = TRUE;
BOOL NNRPTracing = FALSE;
BOOL Tracing = FALSE;
char LogName[] = "ME";
char SPOOL[] = _PATH_SPOOL;
int ErrorCount = IO_ERROR_COUNT;
int MaxIncoming = DEFAULT_CONNECTIONS;
int SPOOLlen = STRLEN(SPOOL);
long LargestArticle = MAX_ART_SIZE;
OPERATINGMODE Mode = OMrunning;
time_t Cutoff = DEFAULT_CUTOFF * 24 * 60 * 60;
#if defined(__CENTERLINE__)
BOOL Debug = TRUE;
#else
BOOL Debug = FALSE;
#endif /* defined(__CENTERLINE__) */
#if defined(lint) || defined(__CENTERLINE__)
int KeepLintQuiet = 0;
#endif /* defined(lint) || defined(__CENTERLINE__) */
STATIC char *ErrlogBuffer;
STATIC char *LogBuffer;
STATIC char ERRLOG[] = _PATH_ERRLOG;
STATIC char INNDDIR[] = _PATH_INNDDIR;
STATIC char LOG[] = _PATH_LOGFILE;
STATIC char PID[] = _PATH_SERVERPID;
STATIC UID_T NewsUID;
STATIC GID_T NewsGID;
/*
** Sprintf a long into a buffer with enough leading zero's so that it
** takes up width characters. Don't add trailing NUL. Return TRUE
** if it fit. Used for updating high-water marks in the active file
** in-place.
*/
BOOL
FormatLong(p, value, width)
register char *p;
register long value;
register int width;
{
for (p += width - 1; width-- > 0; ) {
*p-- = (int)(value % 10) + '0';
value /= 10;
}
return value == 0;
}
/*
** Glue a string, a char, and a string together. Useful for making
** filenames.
*/
void
FileGlue(p, n1, c, n2)
register char *p;
register char *n1;
char c;
register char *n2;
{
p += strlen(strcpy(p, n1));
*p++ = c;
(void)strcpy(p, n2);
}
/*
** Turn any \r or \n in text into spaces. Used to splice back multi-line
** headers into a single line.
*/
STATIC char *
Join(text)
register char *text;
{
register char *p;
for (p = text; *p; p++)
if (*p == '\n' || *p == '\r')
*p = ' ';
return text;
}
/*
** Return a short name that won't overrun our bufer or syslog's buffer.
** q should either be p, or point into p where the "interesting" part is.
*/
char *
MaxLength(p, q)
char *p;
char *q;
{
static char buff[80];
register int i;
/* Already short enough? */
i = strlen(p);
if (i < sizeof buff - 1)
return Join(p);
/* Simple case of just want the begining? */
if (q - p < sizeof buff - 4) {
(void)strncpy(buff, p, sizeof buff - 4);
(void)strcpy(&buff[sizeof buff - 4], "...");
}
/* Is getting last 10 characters good enough? */
else if ((p + i) - q < 10) {
(void)strncpy(buff, p, sizeof buff - 14);
(void)strcpy(&buff[sizeof buff - 14], "...");
(void)strcpy(&buff[sizeof buff - 11], &p[i - 10]);
}
else {
/* Not in last 10 bytes, so use double elipses. */
(void)strncpy(buff, p, sizeof buff - 17);
(void)strcpy(&buff[sizeof buff - 17], "...");
(void)strncpy(&buff[sizeof buff - 14], &q[-5], 10);
(void)strcpy(&buff[sizeof buff - 4], "...");
}
return Join(buff);
}
/*
** Split text into comma-separated fields. Return an allocated
** NULL-terminated array of the fields within the modified argument that
** the caller is expected to save or free. We don't use strchr() since
** the text is expected to be either relatively short or "comma-dense."
*/
char **
CommaSplit(text)
char *text;
{
register int i;
register char *p;
register char **av;
char **save;
/* How much space do we need? */
for (i = 2, p = text; *p; p++)
if (*p == ',')
i++;
for (av = save = NEW(char*, i), *av++ = p = text; *p; )
if (*p == ',') {
*p++ = '\0';
*av++ = p;
}
else
p++;
*av = NULL;
return save;
}
/*
** Do we need a shell for the command? If not, av is filled in with
** the individual words of the command and the command is modified to
** have NUL's inserted.
*/
BOOL
NeedShell(p, av, end)
register char *p;
register char **av;
register char **end;
{
static char Metachars[] = ";<>|*?[]{}()#$&=`'\"\\~\n";
register char *q;
/* We don't use execvp(); works for users, fails out of /etc/rc. */
if (*p != '/')
return TRUE;
for (q = p; *q; q++)
if (strchr(Metachars, *q) != NULL)
return TRUE;
for (end--; av < end; ) {
/* Mark this word, check for shell meta-characters. */
for (*av++ = p; *p && !ISWHITE(*p); p++)
continue;
/* If end of list, we're done. */
if (*p == '\0') {
*av = NULL;
return FALSE;
}
/* Skip whitespace, find next word. */
for (*p++ = '\0'; ISWHITE(*p); p++)
continue;
if (*p == '\0') {
*av = NULL;
return FALSE;
}
}
/* Didn't fit. */
return TRUE;
}
/*
** Spawn a process, with I/O redirected as needed. Return the PID or -1
** (and a syslog'd message) on error.
*/
int
Spawn(fd0, fd1, fd2, av)
int fd0;
int fd1;
int fd2;
char *av[];
{
static char NOCLOSE[] = "%s cant close %d in %s %m";
static char NODUP2[] = "%s cant dup2 %d to %d in %s %m";
int i;
/* Fork; on error, give up. If not using the patched dbz, make
* this call fork! */
i = FORK();
if (i == -1) {
syslog(L_ERROR, "%s cant fork %s %m", LogName, av[0]);
return -1;
}
/* If parent, do nothing. */
if (i > 0)
return i;
/* Child -- do any I/O redirection. */
if (fd0 != 0) {
if (dup2(fd0, 0) < 0) {
syslog(L_FATAL, NODUP2, LogName, fd0, 0, av[0]);
_exit(1);
}
if (fd0 != fd1 && fd0 != fd2 && close(fd0) < 0)
syslog(L_ERROR, NOCLOSE, LogName, fd0, av[0]);
}
if (fd1 != 1) {
if (dup2(fd1, 1) < 0) {
syslog(L_FATAL, NODUP2, LogName, fd1, 1, av[0]);
_exit(1);
}
if (fd1 != fd2 && close(fd1) < 0)
syslog(L_ERROR, NOCLOSE, LogName, fd1, av[0]);
}
if (fd2 != 2) {
if (dup2(fd2, 2) < 0) {
syslog(L_FATAL, NODUP2, LogName, fd2, 2, av[0]);
_exit(1);
}
if (close(fd2) < 0)
syslog(L_ERROR, NOCLOSE, LogName, fd2, av[0]);
}
CloseOnExec(0, FALSE);
CloseOnExec(1, FALSE);
CloseOnExec(2, FALSE);
/* Try to set our permissions. */
#if defined(DO_INND_NICE_KIDS)
(void)nice(INND_NICE_VALUE);
#endif /* defined(DO_INND_NICE_KIDS) */
if (setgid(NewsGID) == -1)
syslog(L_ERROR, "%s cant setgid in %s %m", LogName, av[0]);
if (setuid(NewsUID) == -1)
syslog(L_ERROR, "%s cant setuid in %s %m", LogName, av[0]);
/* Close the DBZ database without doing any writing. */
/* Not needed with the patched DBZ; can't be used with vfork.
* (void)dbzcancel();
* (void)dbmclose();
*/
/* Start the desired process (finally!). */
(void)execv(av[0], av);
syslog(L_FATAL, "%s cant exec in %s %m", LogName, av[0]);
_exit(1);
/* NOTREACHED */
}
/*
** Stat our control directory and see who should own things.
*/
STATIC BOOL
GetNewsOwnerships()
{
struct stat Sb;
/* Make sure item exists and is of the right type. */
if (stat(INNDDIR, &Sb) < 0)
return FALSE;
if (!S_ISDIR(Sb.st_mode))
return FALSE;
NewsUID = Sb.st_uid;
NewsGID = Sb.st_gid;
return TRUE;
}
/*
** Change the onwership of a file.
*/
void
xchown(p)
char *p;
{
if (chown(p, NewsUID, NewsGID) < 0)
syslog(L_ERROR, "%s cant chown %s %m", LogName, p);
}
/*
** Try to make one directory. Return FALSE on error.
*/
STATIC BOOL
MakeDir(Name)
char *Name;
{
struct stat Sb;
if (mkdir(Name, GROUPDIR_MODE) >= 0) {
if (AmRoot)
xchown(Name);
return TRUE;
}
/* See if it failed because it already exists. */
return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
}
/*
** Given a directory, comp/foo/bar, create that directory and all
** intermediate directories needed. Return 0 if ok, else -1.
*/
BOOL
MakeSpoolDirectory(Name)
register char *Name;
{
register char *p;
BOOL made;
/* Optimize common case -- parent almost always exists. */
if (MakeDir(Name))
return TRUE;
/* Try to make each of comp and comp/foo in turn. */
for (p = Name; *p; p++)
if (*p == '/') {
*p = '\0';
made = MakeDir(Name);
*p = '/';
if (!made)
return FALSE;
}
return MakeDir(Name);
}
/*
** Flush one log file, with pessimistic size of working filename buffer.
*/
void
ReopenLog(F)
FILE *F;
{
char buff[sizeof LOG + sizeof ERRLOG + 4 + 1];
char *Name;
char *Buffer;
int mask;
if (Debug)
return;
if (F == Log) {
Name = LOG;
Buffer = LogBuffer;
}
else {
Name = ERRLOG;
Buffer = ErrlogBuffer;
}
FileGlue(buff, Name, '.', "old");
if (rename(Name, buff) < 0)
syslog(L_ERROR, "%s cant rename %s to %s %m", LogName, Name, buff);
mask = umask(033);
if (freopen(Name, "a", F) != F) {
syslog(L_FATAL, "%s cant freopen %s %m", LogName, Name);
exit(1);
}
(void)umask(mask);
if (AmRoot)
xchown(Name);
if (BufferedLogs)
SETBUFFER(F, Buffer, LogBufferSize);
}
/*
** Function called when memory allocation fails.
*/
STATIC int
AllocationFailure(what, i)
char *what;
unsigned int i;
{
/* Print i as %d so huge values are real obvious. */
syslog(L_FATAL, "%s cant %s %d bytes %m", LogName, what, i);
exit(1);
/* NOTREACHED */
}
/*
** We ran out of space or other I/O error, throttle ourselves.
*/
void
ThrottleIOError(when)
char *when;
{
char buff[SMBUF];
STRING p;
int oerrno;
if (Mode == OMrunning) {
oerrno = errno;
if (Reservation) {
DISPOSE(Reservation);
Reservation = NULL;
}
(void)sprintf(buff, "%s writing %s file -- throttling",
strerror(oerrno), when);
if ((p = CCblock(OMthrottled, buff)) != NULL)
syslog(L_ERROR, "%s cant throttle %s", LogName, p);
syslog(L_FATAL, "%s throttle %s", LogName, buff);
errno = oerrno;
}
}
/*
** Close down all parts of the system (e.g., before calling exit or exec).
*/
void
JustCleanup()
{
SITEflushall(FALSE);
/* PROCclose(FALSE); */
CCclose();
LCclose();
NCclose();
RCclose();
ICDclose();
HISclose();
ARTclose();
(void)sleep(1);
/* PROCclose(TRUE); */
if (unlink(PID) < 0 && errno != ENOENT)
syslog(L_ERROR, "%s cant unlink %s %m", LogName, PID);
}
/*
** The name is self-explanatory.
*/
NORETURN
CleanupAndExit(x, why)
int x;
char *why;
{
JustCleanup();
syslog(L_NOTICE, "%s shutdown %s", LogName, why);
exit(x);
}
#if NOFILE_LIMIT > 0
/*
** Set the limit on the number of open files we can have. I don't
** like having to do this.
*/
STATIC void
SetDescriptorLimit(i)
int i;
{
struct rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
syslog(L_ERROR, "%s cant getrlimit(NOFILE) %m", LogName);
return;
}
rl.rlim_cur = i;
if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
syslog(L_ERROR, "%s cant setrlimit(NOFILE) %d %m", LogName, i);
return;
}
}
#endif /* NOFILE_LIMIT > 0 */
/*
** Signal handler to catch SIGTERM and queue a clean shutdown.
*/
STATIC SIGHANDLER
CatchTerminate(s)
int s;
{
GotTerminate = TRUE;
(void)signal(s, CatchTerminate);
}
/*
** Print a usage message and exit.
*/
STATIC NORETURN
Usage()
{
(void)fprintf(stderr, "Usage error.\n");
exit(1);
}
int
main(ac, av)
int ac;
char *av[];
{
static char WHEN[] = "PID file";
int i;
int port;
int logflags;
char buff[SMBUF];
char *master;
STRING path;
STRING p;
FILE *F;
BOOL ShouldFork;
BOOL ShouldRenumber;
BOOL ShouldSyntaxCheck;
long pid;
#if defined(_DEBUG_MALLOC_INC)
union malloptarg m;
#endif /* defined(_DEBUG_MALLOC_INC) */
/* Set up the pathname, first thing. */
path = av[0];
if (path == NULL || *path == '\0')
path = "innd";
else if ((p = strrchr(path, '/')) != NULL)
path = p + 1;
ONALLLOCFAIL(AllocationFailure);
Version = INNVersion();
/* Handle malloc debugging. */
#if defined(_DEBUG_MALLOC_INC)
m.i = M_HANDLE_ABORT;
dbmallopt(MALLOC_WARN, &m);
dbmallopt(MALLOC_FATAL, &m);
m.i = 3;
dbmallopt(MALLOC_FILLAREA, &m);
m.i = 0;
dbmallopt(MALLOC_CKCHAIN, &m);
dbmallopt(MALLOC_CKDATA, &m);
#endif /* defined(_DEBUG_MALLOC_INC) */
/* Set defaults. */
TimeOut.tv_sec = DEFAULT_TIMEOUT;
ShouldFork = TRUE;
ShouldRenumber = FALSE;
ShouldSyntaxCheck = FALSE;
logflags = L_OPENLOG_FLAGS | LOG_NOWAIT;
port = -1;
master = NULL;
#if defined(DONT_ALLOW_READERS)
NNRPFollows = TRUE;
#endif /* defined(DONT_ALLOW_READERS) */
#if defined(DO_FAST_RESOLV)
/* We only use FQDN's in the hosts.nntp file. */
_res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
#endif /* defined(DO_FAST_RESOLV) */
/* Parse JCL. */
CCcopyargv(av);
while ((i = getopt(ac, av, "ac:dfi:l:m:o:n:p:rsS:t:ux")) != EOF)
switch (i) {
default:
Usage();
/* NOTREACHED */
case 'a':
AnyIncoming = TRUE;
break;
case 'c':
Cutoff = atoi(optarg) * 24 * 60 * 60;
break;
case 'd':
Debug = TRUE;
#if defined(LOG_PERROR)
logflags = LOG_PERROR | (logflags & ~LOG_CONS);
#endif /* defined(LOG_PERROR) */
break;
case 'f':
ShouldFork = FALSE;
break;
case 'i':
MaxIncoming = atoi(optarg);
break;
case 'l':
LargestArticle = atol(optarg);
break;
case 'm':
if (ModeReason)
DISPOSE(ModeReason);
switch (*optarg) {
default:
Usage();
/* NOTREACHED */
case 'g': Mode = OMrunning; break;
case 'p': Mode = OMpaused; break;
case 't': Mode = OMthrottled; break;
}
if (Mode != OMrunning) {
(void)sprintf(buff, "%sed from command line",
Mode == OMpaused ? "Paus" : "Throttl");
ModeReason = COPY(buff);
}
break;
case 'n':
switch (*optarg) {
default:
Usage();
/* NOTREACHED */
case 'n': NNRPFollows = TRUE; break;
case 'y': NNRPFollows = FALSE; break;
}
break;
case 'o':
MaxOutgoing = atoi(optarg);
break;
case 'p':
/* Silently ignore multiple -p flags, in case ctlinnd xexec
* called inndstart. */
if (port == -1) {
port = atoi(optarg);
AmRoot = FALSE;
}
break;
case 'r':
ShouldRenumber = TRUE;
break;
case 's':
ShouldSyntaxCheck = TRUE;
break;
case 'S':
master = optarg;
break;
case 't':
TimeOut.tv_sec = atol(optarg);
break;
case 'u':
BufferedLogs = FALSE;
break;
case 'x':
AlwaysCrosspost = TRUE;
break;
}
ac -= optind;
if (ac != 0)
Usage();
if (ModeReason && NNRPFollows)
NNRPReason = COPY(ModeReason);
openlog(path, logflags, LOG_INN_SERVER);
if (ShouldSyntaxCheck) {
if ((p = CCcheckfile((char **)NULL)) == NULL)
exit(0);
(void)fprintf(stderr, "%s\n", p + 2);
}
/* Go to where the data is. */
if (chdir(SPOOL) < 0) {
syslog(L_FATAL, "%s cant chdir %s %m", LogName, SPOOL);
exit(1);
}
/* Get the Path entry. */
if ((path = GetConfigValue(_CONF_PATHHOST)) == NULL) {
syslog(L_FATAL, "%s cant GetConfigValue %s %m",
LogName, _CONF_PATHHOST);
exit(1);
}
Path.Used = strlen(path) + 1;
Path.Data = NEW(char, Path.Used + 1);
(void)sprintf(Path.Data, "%s!", path);
/* Get the Xref prefix. */
Xref.Size = SMBUF;
Xref.Data = NEW(char, Xref.Size);
(void)sprintf(Xref.Data, "Xref: %s", path);
Xref.Used = strlen(Xref.Data);
Xrefbase = Xref.Used;
#if !defined(__CENTERLINE__)
/* Set standard input to /dev/null. */
if ((i = open("/dev/null", O_RDWR)) < 0) {
syslog(L_FATAL, "%s cant open /dev/null %m", LogName);
exit(1);
}
if (dup2(i, 0) != 0)
syslog(L_NOTICE, "%s cant dup2 %d to 0 %m", LogName, i);
(void)close(i);
#endif /* !defined(__CENTERLINE__) */
/* Set up our permissions. */
(void)umask(NEWSUMASK);
if (!GetNewsOwnerships()) {
syslog(L_FATAL, "%s internal cant stat control directory %m", LogName);
exit(1);
}
if (port != -1 && setgid(NewsGID) < 0)
syslog(L_ERROR, "%s cant setgid running as %d not %d %m",
LogName, (int)getgid(), (int)NewsGID);
if (Debug) {
Log = stdout;
Errlog = stderr;
(void)signal(SIGINT, CatchTerminate);
}
else {
if (ShouldFork) {
/* Become a server. */
i = fork();
if (i < 0) {
syslog(L_FATAL, "%s cant fork %m", LogName);
exit(1);
}
if (i > 0)
_exit(0);
#if defined(TIOCNOTTY)
/* Disassociate from terminal. */
if ((i = open("/dev/tty", O_RDWR)) >= 0) {
if (ioctl(i, TIOCNOTTY, (char *)NULL) < 0)
syslog(L_ERROR, "%s cant ioctl(TIOCNOTTY) %m", LogName);
if (close(i) < 0)
syslog(L_ERROR, "%s cant close /dev/tty %m", LogName);
}
#endif /* defined(TIOCNOTTY) */
#if defined(DO_HAVE_SETSID)
(void)setsid();
#endif /* defined(DO_HAVE_SETSID) */
}
/* Open the Log. */
(void)fclose(stdout);
if ((Log = fopen(LOG, "a")) == NULL) {
syslog(L_FATAL, "%s cant fopen %s %m", LogName, LOG);
exit(1);
}
if (AmRoot)
xchown(LOG);
if (BufferedLogs && (LogBuffer = NEW(char, LogBufferSize)) != NULL)
SETBUFFER(Log, LogBuffer, LogBufferSize);
/* Open the Errlog. */
(void)fclose(stderr);
if ((Errlog = fopen(ERRLOG, "a")) == NULL) {
syslog(L_FATAL, "%s cant fopen %s %m", LogName, ERRLOG);
exit(1);
}
if (AmRoot)
xchown(ERRLOG);
if (BufferedLogs && (ErrlogBuffer = NEW(char, LogBufferSize)) != NULL)
SETBUFFER(Errlog, ErrlogBuffer, LogBufferSize);
}
/* Set number of open channels. */
#if NOFILE_LIMIT > 0
if (AmRoot)
SetDescriptorLimit(NOFILE_LIMIT);
#endif /* NOFILE_LIMIT > 0 */
/* Get number of open channels. */
if ((i = getfdcount()) < 0) {
syslog(L_FATAL, "%s cant getfdcount %m", LogName);
exit(1);
}
syslog(L_NOTICE, "%s descriptors %d", LogName, i);
if (MaxOutgoing == 0) {
/* getfdcount() - (stdio + dbz + cc + lc + rc + art + fudge) */
MaxOutgoing = i - ( 3 + 3 + 2 + 1 + 1 + 1 + 2 );
syslog(L_NOTICE, "%s outgoing %d", LogName, MaxOutgoing);
}
/* See if another instance is alive. */
if ((F = fopen(PID, "r")) != NULL) {
if (fgets(buff, sizeof buff, F) != NULL
&& ((pid = atoi(buff)) > 0)
&& (kill((PID_T)pid, 0) > 0 || errno != ESRCH)) {
(void)syslog(L_FATAL, "%s already_running pid %d", LogName, pid);
exit(1);
}
(void)fclose(F);
}
/* Set up the various parts of the system. Channel feeds start
* processes so call PROCsetup before ICDsetup. NNTP needs to know
* if it's a slave, so call RCsetup before NCsetup. */
(void)signal(SIGTERM, CatchTerminate);
#if defined(SIGDANGER)
(void)signal(SIGDANGER, CatchTerminate);
#endif /* defined(SIGDANGER) */
CHANsetup(i);
PROCsetup(i * 2);
HISsetup();
CCsetup();
LCsetup();
RCsetup(port, master);
NCsetup(i);
ARTsetup();
ICDsetup(TRUE);
#if defined(_DEBUG_MALLOC_INC)
m.i = 1;
dbmallopt(MALLOC_CKCHAIN, &m);
dbmallopt(MALLOC_CKDATA, &m);
#endif /* defined(_DEBUG_MALLOC_INC) */
/* Record our PID. */
pid = getpid();
if ((F = fopen(PID, "w")) == NULL) {
IOError(WHEN);
syslog(L_ERROR, "%s cant fopen %s %m", LogName, PID);
}
else {
if (fprintf(F, "%ld\n", (long)pid) == EOF || ferror(F)) {
IOError(WHEN);
syslog(L_ERROR, "%s cant fprintf %s %m", LogName, PID);
}
if (fclose(F) == EOF) {
IOError(WHEN);
syslog(L_ERROR, "%s cant fclose %s %m", LogName, PID);
}
if (chmod(PID, 0664) < 0) {
IOError(WHEN);
syslog(L_ERROR, "%s cant chmod %s %m", LogName, PID);
}
}
/* And away we go... */
if (ShouldRenumber) {
syslog(L_NOTICE, "%s renumbering", LogName);
ICDrenumberactive();
}
syslog(L_NOTICE, "%s starting", LogName);
CHANreadloop();
CleanupAndExit(1, "CHANreadloop returned");
/* NOTREACHED */
}